package com.example.guetdemo.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.guetdemo.domain.dto.CodeDto;
import com.example.guetdemo.domain.dto.LoginDto;
import com.example.guetdemo.domain.dto.RegisterDto;
import com.example.guetdemo.domain.entity.User;
import com.example.guetdemo.domain.vo.UserLoginVo;
import com.example.guetdemo.domain.vo.UserVo;
import com.example.guetdemo.enums.AppHttpCodeEnum;
import com.example.guetdemo.mapper.UserMapper;
import com.example.guetdemo.result.ResponseResult;
import com.example.guetdemo.service.UserService;
import com.example.guetdemo.utils.JwtUtil;
import com.example.guetdemo.utils.RedisCache;
import com.example.guetdemo.utils.SHA256;
import com.example.guetdemo.utils.WebUtils;
import com.example.guetdemo.websocket.WebSocketServer;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import io.jsonwebtoken.Claims;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import pub.telephone.javahttprequest.network.http.HTTPMethod;
import pub.telephone.javahttprequest.network.http.HTTPRequest;
import pub.telephone.javahttprequest.network.mime.MIMEType;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;

import static com.example.guetdemo.constants.SystemConstants.*;
import static java.util.concurrent.TimeUnit.SECONDS;

/**
 * (User)表服务实现类
 */
@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisCache redisCache;

    @Resource
    private JwtUtil jwtUtil;

    /**
     * 注册
     * @param registerDto
     * @return
     */
    @Override
    public ResponseResult register(RegisterDto registerDto) {
        //对数据进行是否存在判断,如果该用户名在数据库中已经存在，则该用户名无法注册
        if (!judgeUsername(registerDto.getUserName())) {
            return ResponseResult.errorResult(AppHttpCodeEnum.USERNAME_EXIST);
        }
        //密码加密存储
        String EncryptedPassword = SHA256.getSHA256Str(registerDto.getPassword());;

        User user = new User();
        //BeanUtils.copyProperties(user,registerDto,"password");——尝试该方法不可行（浅浅的估计是浅拷贝的问题），实际上应该可以通过序列化与反序列化的方式实现（深拷贝）
        user.setUserName(registerDto.getUserName());
        user.setNickName(registerDto.getNickName());
        user.setPassword(EncryptedPassword);

        //存入数据库
        save(user);

        return ResponseResult.okResult("注册成功");
    }

    /**
     * 登录
     * @param loginDto
     * @return
     */
    @Override
    public ResponseResult userLogin(LoginDto loginDto, HttpSession httpSession, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        //如果头部缺失”login_type“
        if (!StringUtils.hasText(httpServletRequest.getHeader("login_type"))) {
            return ResponseResult.errorResult(AppHttpCodeEnum.HTTP_HEADER_LOGIN_TYPE_NOT_FIND);
        }
        String loginType = httpServletRequest.getHeader("login_type");
        //当头部字段”login_type“为"VPN-PWD",向教务系统发送请求
        if (loginType.equals("VPN-PWD")) {
            String requestBody = JSON.toJSONString(loginDto);
            String[] res = new String[1];//为什么String类型不可以，但是String数组就行呢，再看看
            new HTTPRequest(HTTPMethod.POST, "http://localhost:7777/users/test2")
                    .SetRequestContentType(MIMEType.ApplicationJSON)
                    .SetRequestString(requestBody)
                    //自定义设置请求头字段
                    .SetCustomizedHeaderList(Collections.singletonList(
                            new String[]{"login_type", "VPN-PWD"}
                    ))
                    //期望返回的类型？
                    .String()
                    .Then(strRes -> {
                        //拿到回调的头中的交互类型"Blocker-Type"
                        //一次性验证成功
                        if(strRes.Request.GetHeader("Blocker-Type").get(0).equals("success")) {
                            //假设返回的用户信息是：
                            UserVo userVo = new UserVo(9999,"lidap","3314795756");
                            String userAgent = httpServletRequest.getHeader("User-Agent");
                            String loginType2 = "password";
                            String jwt = JwtUtil.createJWT(httpSession.getId(),JwtUtil.JWT_TTL);
                            UserLoginVo userLoginVo = new UserLoginVo(jwt,userAgent,loginType2,userVo);
                            //session存储用户信息
//                            httpSession.setAttribute("vpnUser",strRes.Result);
                            httpSession.setAttribute("User",userLoginVo);
                            //存入redis,sessionID 作为key,userVo作为值
                            redisCache.setCacheObject("guetUser"+httpSession.getId(),userLoginVo);
                            redisCache.expire("guetUser"+httpSession.getId(),60*60);
                            res[0] = jwt;
                        }
                        //需要扫码登录，扫码应该就是一个新的请求了
                        if(strRes.Request.GetHeader("Blocker-Type").get(0).equals("needScan")) {
                            res[0] = strRes.Result;
                        }
                        //教务验证用户名或密码错误
                        if(strRes.Request.GetHeader("Blocker-Type").get(0).equals("error")) {
                            WebUtils.setStatus(httpServletResponse,AppHttpCodeEnum.USERNAME_NOT_EXIST.getCode());
                            res[0] = strRes.Result+"(用户名或密码错误)";
                        }
                        return 0;
                    }).Catch(throwable -> {
                        //当教务服务发生问题时
                        throwable.printStackTrace();
                        res[0] = "教务验证服务未响应";
                        WebUtils.setStatus(httpServletResponse,AppHttpCodeEnum.USERNAME_NOT_EXIST.getCode());
                        return 0;
                    }).Await();
            //是通过这一条返回的,返回成功
            return ResponseResult.okResult(res[0]);
        }
        //当头部字段”login_type“为"COB-PWD"
        if(loginType.equals("COB-PWD")) {
            //验证用户名密码,VALIDATE_USERNAME_ERROR表示用户名错误，VALIDATE_PASSWORD_ERROR表示用户名正确的情况下密码错误,VALIDATE_SUCCESS为验证成功
            //校验标记
            int flag = validateUser(loginDto);
            if(flag == VALIDATE_USERNAME_ERROR) {
                return ResponseResult.errorResult(AppHttpCodeEnum.USERNAME_NOT_EXIST);
            }
            if (flag == VALIDATE_PASSWORD_ERROR) {
                return ResponseResult.errorResult(AppHttpCodeEnum.PASSWORD_ERROR);
            }

            //获取用户id用于生成token
            LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(User::getUserName,loginDto.getUserName());
            User user = userMapper.selectOne(lambdaQueryWrapper);
            String userId = user.getId().toString();
            String jwt = JwtUtil.createJWT(userId,JwtUtil.JWT_TTL);

            UserVo userVo = new UserVo(user.getId(),user.getNickName(),user.getUserName());

            //用户信息存入session
            httpSession.setAttribute("User",userVo);

            //把用户信息存入redis,并封装UserLoginVO
            String userAgent = httpServletRequest.getHeader("User-Agent");
            String loginType2 = "password";
            UserLoginVo userLoginVo = new UserLoginVo(jwt,userAgent,loginType2,userVo);
            redisCache.setCacheObject("guetUser"+userId,userLoginVo);
            redisCache.expire("guetUser"+userId,60*60);
            return ResponseResult.okResult(userLoginVo);
        }
        WebUtils.setStatus(httpServletResponse,AppHttpCodeEnum.HTTP_HEADER_LOGIN_TYPE_ERROR.getCode());
        return ResponseResult.errorResult(AppHttpCodeEnum.HTTP_HEADER_LOGIN_TYPE_ERROR);
    }

    /**
     * 认证测试
     * @return
     */
    @Override
    public ResponseResult getUserList() {
        List<User> users = list();
        return ResponseResult.okResult(users);
    }

    /**
     * 发送短信验证码
     * @param codeDto
     * @return
     */
    @Override
    public ResponseResult sendCode(CodeDto codeDto) {

        //如果二分钟前发送过验证码则不能再发送
        if(Objects.nonNull(redisCache.getCacheObject(codeDto.getPhoneNumber()))) {
            return ResponseResult.errorResult(AppHttpCodeEnum.VERIFICATION_CODE_EXIST);
        }

        // 生成一个随机数生成器
        Random random = new Random();
        // 生成一个6位数的验证码
        String verificationCode = "";
        for (int i = 0; i < 6; i++) {
            verificationCode += random.nextInt(10);
        }
        String code = verificationCode;

        //缓存随机验证码，并且设置过期时间
        redisCache.setCacheObject(codeDto.getPhoneNumber(),code,120,SECONDS);

        System.out.println(code+"我发送了验证码");
        return ResponseResult.okResult(code);
    }

    /**
     * 校验短信验证码
     * @param codeDto
     * @return
     */
    @Override
    public ResponseResult verifyCode(CodeDto codeDto) {
        //用户输入的验证码与缓存中的验证码进行比对
        String redisCode = redisCache.getCacheObject(codeDto.getPhoneNumber());
        if (codeDto.getCode().equals(redisCode)) {
            return ResponseResult.okResult();
        }
        return ResponseResult.errorResult(AppHttpCodeEnum.VERIFICATION_CODE_EXPIRED);
    }

    /**
     * 根据 uuid生成二维码
     * @return
     */
    @Override
    public ResponseEntity<byte[]> generateQRUUID(HttpServletResponse httpServletResponse) throws IOException, WriterException {
        //生成uuid
        String uuid = UUID.randomUUID().toString();
        System.out.println(uuid);
        //将标识符存储到redis，设置两分钟过期
        redisCache.setCacheObject(uuid,"false",120, SECONDS);
        //生成二维码图片
        int width = 200;
        int height = 200;
        String format = "png";
        Map<EncodeHintType, Object> hints = new HashMap<>();
        hints.put(EncodeHintType.CHARACTER_SET,"UTF-8");
        BitMatrix bitMatrix = new MultiFormatWriter().encode(uuid, BarcodeFormat.QR_CODE,width,height,hints);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        MatrixToImageWriter.writeToStream(bitMatrix,format,out);
        byte[] bytes = out.toByteArray();
        //返回二维码
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.IMAGE_PNG);
        //前端用二维码uuid建立websocket连接
        return new ResponseEntity<>(bytes,headers, HttpStatus.OK);
    }

    /**
     * 扫描二维码二维码状态改变
     * @param httpServletRequest
     * @param httpServletResponse
     * @return
     */
    @Override
    public ResponseResult scanQRCode(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
        //从请求头中获取二维码的uuid
        String qrUUID = httpServletRequest.getHeader("qrcode-uuid");

        //从redis中取出标识符，将二维码状态修改为待确认，表示扫码成功
        String value = redisCache.getCacheObject(qrUUID);
        //为空可能是二维码过期，可能已经被扫描登录，目前都显示过期好了
        if (value == null) {
            WebUtils.setStatus(httpServletResponse,AppHttpCodeEnum.QRCODE_EXPIRE.getCode());
            //向二维码uuid所建立的websocket连接发送消息，发送二维码已经过期，前端自动刷新二维码
            WebSocketServer.sendInfo("二维码已经过期",qrUUID);
            return ResponseResult.errorResult(AppHttpCodeEnum.QRCODE_EXPIRE);
        }
        //二维码不能重复扫描,不过第一次扫描的时候已经将扫描二维码的登录的用户的信息返回给前端了，前端会切换扫描界面为确认界面，应该也不会出现重复扫描的情况
        if (value.equals("toBeConfirmed")) {
            WebUtils.setStatus(httpServletResponse,400);
            WebSocketServer.sendInfo("此二维码已经被扫描",qrUUID);
            return ResponseResult.errorResult(400,"此二维码已经被扫描");
        }

        //拿到扫码用户的token，解析用户信息
        String phoneUserJwt = httpServletRequest.getHeader("token");
        //解析获取userid
        Claims claims = null;
        try {
            claims = jwtUtil.parseJWT(phoneUserJwt);
        } catch (Exception e) {
            e.printStackTrace();
            //token无法解析 token超时  token非法
        }
        String phoneUserId = claims.getSubject();
        //从redis中获取用户信息
        UserLoginVo phoneUserLoginVo = redisCache.getCacheObject("guetUser" + phoneUserId);
        UserVo phoneUserVo = phoneUserLoginVo.getUserVo();

        //扫码成功后提供用户一分钟确认时间
        redisCache.setCacheObject(qrUUID,"toBeConfirmed",60, SECONDS);

        //向二维码uuid所建立的websocket连接发送消息
        String userVo = JSON.toJSONString(phoneUserVo);
        WebSocketServer.sendInfo(userVo,qrUUID);
        return ResponseResult.okResult(phoneUserVo);
    }

    /**
     * 轮询方式确认二维码登录
     * @param httpServletRequest
     * @param httpServletResponse
     * @return
     */
    @Override
    public ResponseResult confrimQRLogin(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        //拿到二维码标识
        String uuid = httpServletRequest.getHeader("qrcode-uuid");
        //拿到扫码用户的jwt
        String jwt = httpServletRequest.getHeader("token");
        //从redis中取出标识符，并将其值修改为认证的用户信息，表示扫码成功
        String value = redisCache.getCacheObject(uuid);
        if (value == null) {
            WebUtils.setStatus(httpServletResponse,AppHttpCodeEnum.QRCODE_EXPIRE.getCode());
            return ResponseResult.errorResult(AppHttpCodeEnum.QRCODE_EXPIRE);
        }
        //二维码如果没被扫描（是否多余呢，如果用户不扫描就不可能有正确的qrUUID）
        if (value.equals("false")) {
            WebUtils.setStatus(httpServletResponse,400);
            return ResponseResult.errorResult(400,"此二维码还未被扫描无法确认");
        }
        //确认成功
        redisCache.setCacheObject(uuid,jwt);
        return ResponseResult.okResult("用户确认登录，等待前端轮询");
    }

    /**
     * websocket方式确认登录
     * @param httpServletRequest
     * @param httpServletResponse
     * @return
     */
    @Override
    public ResponseResult confirmQRLoginWebsocket(HttpSession httpSession ,HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
        String uuid = httpServletRequest.getHeader("qrcode-uuid");
        //获取授权用户的登录信息
        String phoneUserJwt = httpServletRequest.getHeader("token");
        //解析获取userid
        Claims claims = null;
        try {
            claims = jwtUtil.parseJWT(phoneUserJwt);
        } catch (Exception e) {
            e.printStackTrace();
            //token无法解析 token超时  token非法
        }
        String phoneUserId = claims.getSubject();
        //从redis中获取用户信息
        UserLoginVo phoneUserLoginVo = redisCache.getCacheObject("guetUser" + phoneUserId);

        //构建pc用户登录
        String userAgent = phoneUserLoginVo.getUserAgent();
        String loginType2 = "scanQRCode";
        UserVo userVo = phoneUserLoginVo.getUserVo();
        String jwt = JwtUtil.createJWT(httpSession.getId(),JwtUtil.JWT_TTL);
        UserLoginVo userLoginVo = new UserLoginVo(jwt,userAgent,loginType2,userVo);
        //session存储用户信息
        httpSession.setAttribute("User",userLoginVo);
        //存入redis中
        redisCache.setCacheObject("guetUser"+httpSession.getId(),userLoginVo);
        redisCache.expire("guetUser"+httpSession.getId(),60*60);
        System.out.println(httpSession.getAttribute("User")+"会话中的用户信息");

        //websocket告知前端
        String userLoginInfo = JSON.toJSONString(userLoginVo);
        WebSocketServer.sendInfo(userLoginInfo,uuid);

        //删除二维码
        redisCache.deleteObject(uuid);
        return ResponseResult.okResult("确认登录成功");
    }

    /**
     * 客户端轮询二维码状态
     * @param httpSession
     * @param httpServletRequest
     * @param httpServletResponse
     * @return
     */
    @Override
    public ResponseResult checkQRCode(HttpSession httpSession, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        String uuid = httpServletRequest.getHeader("qrcode-uuid");
        String value = redisCache.getCacheObject(uuid);
        if (value == null) {
            WebUtils.setStatus(httpServletResponse,AppHttpCodeEnum.QRCODE_EXPIRE.getCode());
            return ResponseResult.errorResult(AppHttpCodeEnum.QRCODE_EXPIRE);
        }

        if(value.equals("false")) {
            WebUtils.setStatus(httpServletResponse,400);
            return ResponseResult.errorResult(400,"没有用户扫码二维码");
        }

        if (value.equals("toBeConfirmed")) {
            WebUtils.setStatus(httpServletResponse,400);
            return ResponseResult.errorResult(400,"用户扫码了二维码，待确认登录");
        }

        //一下说明用户确认进行登录
        //获取授权用户的登录信息
        String phoneUserJwt = redisCache.getCacheObject(uuid);
        //解析获取userid
        Claims claims = null;
        try {
            claims = jwtUtil.parseJWT(phoneUserJwt);
        } catch (Exception e) {
            e.printStackTrace();
            //token无法解析 token超时  token非法
        }
        String phoneUserId = claims.getSubject();
        //从redis中获取用户信息
        UserLoginVo phoneUserLoginVo = redisCache.getCacheObject("guetUser" + phoneUserId);

        //构建pc用户登录
        String userAgent = phoneUserLoginVo.getUserAgent();
        String loginType2 = "scanCode";
        UserVo userVo = phoneUserLoginVo.getUserVo();
        String jwt = JwtUtil.createJWT(httpSession.getId(),JwtUtil.JWT_TTL);
        UserLoginVo userLoginVo = new UserLoginVo(jwt,userAgent,loginType2,userVo);
        //session存储用户信息
        httpSession.setAttribute("User",userLoginVo);
        //存入redis中
        redisCache.setCacheObject("guetUser"+httpSession.getId(),userLoginVo);
        redisCache.expire("guetUser"+httpSession.getId(),60*60);
        System.out.println(httpSession.getAttribute("User")+"会话中的用户信息");

        //删除二维码
        redisCache.deleteObject(uuid);
        return ResponseResult.okResult(userLoginVo);
    }


    //-----------------------------------------------------------------------------一些方法

    /**
     * 判断用户名是否存在
     * @param username
     * @return
     */
    public boolean judgeUsername(String username) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUserName, username);
        User user = getOne(queryWrapper);
        if (Objects.isNull(user)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 校验用户名密码正确性
     * @param loginDto
     * @return
     */
    private int validateUser(LoginDto loginDto) {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getUserName,loginDto.getUserName());
        User user1 = userMapper.selectOne(lambdaQueryWrapper);
        if(Objects.isNull(user1)) {
            return VALIDATE_USERNAME_ERROR;
        }
        //用加密后的密码与数据库密码进行匹配
        String EncryptedPassword = SHA256.getSHA256Str(loginDto.getPassword());
        lambdaQueryWrapper.eq(User::getUserName,loginDto.getUserName());
        lambdaQueryWrapper.eq(User::getPassword,EncryptedPassword);
        User user2 = userMapper.selectOne(lambdaQueryWrapper);
        if(Objects.isNull(user2)) {
            return VALIDATE_PASSWORD_ERROR;
        }
        return VALIDATE_SUCCESS;
    }
}
